﻿using System;
using System.Collections.Generic;
using System.Text;
using WiimoteLib;

namespace WiiMouseLib
{
    public class WiiMouse : System.ComponentModel.Component, IDisposable, System.ComponentModel.INotifyPropertyChanged
    {
        //Private fields
        #region Private Fields
        Wiimote wm;
        float irCursorX = 0;
        float irCursorY = 0;
        float[] irCursorXHist = new float[30];
        float[] irCursorYHist = new float[30];
        bool leftClickDown = false;
        bool rightClickDown = false;
        bool oneDown = false;
        bool twoDown = false;
        bool plusDown = false;
        bool minusDown = false;
        int plusSpeed = 1;
        bool invertX;
        bool invertY;
        System.ComponentModel.ISynchronizeInvoke sync;
        bool connected;
        DateTime leftTick, rightTick, upTick, downTick;
        #endregion

        #region Properties
        /// <summary>
        /// The plus speed ??
        /// </summary>
        public int PlusSpeed 
        {
            get
            {
                return plusSpeed;
            }
            set
            {
                plusSpeed = value;
                NotifyPropertyChanged("PlusSpeed");
                System.Diagnostics.Debug.WriteLine("Set PlusSpeed to " + value.ToString());
            }
        }

        /// <summary>
        /// Gets/Sets the Invert X axis flag
        /// </summary>
        public bool InvertX
        {
            get
            {
                return !invertX;
            }
            set
            {
                invertX = !value;
                NotifyPropertyChanged("InvertX");
                System.Diagnostics.Debug.WriteLine("Set InvertX to " + value.ToString());
            }
        }

        /// <summary>
        /// Gets/Sets the Invert Y axis flag
        /// </summary>
        public bool InvertY
        {
            get
            {
                return invertY;
            }
            set
            {
                invertY = value;
                NotifyPropertyChanged("InvertY");
                System.Diagnostics.Debug.WriteLine("Set InvertY to " + value.ToString());
            }
        }

        /// <summary>
        /// Gets the X origin.
        /// </summary>
        private int XOrg
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.X;
            }
        }

        /// <summary>
        /// Gets the X max
        /// </summary>
        private int XMax
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.Right;
            }
        }

        private int XWidth
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.Width;
            }
        }

        /// <summary>
        /// Gets the Y origin.
        /// </summary>
        private int YOrg
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.Y;
            }
        }

        /// <summary>
        /// Gets the Y max
        /// </summary>
        private int YMax
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.Bottom;
            }
        }

        private int YWidth
        {
            get
            {
                return System.Windows.Forms.SystemInformation.VirtualScreen.Height;
            }
        }

        #endregion

        public WiiMouse()
        {
            this.InvertX = false;
            this.InvertY = false;
            this.connected = false;
            this.PlusSpeed = 1;
            this.leftTick = DateTime.Now;
            this.rightTick = DateTime.Now;
            this.upTick = DateTime.Now;
            this.downTick = DateTime.Now;

            wm = new Wiimote();
        }

        public void connect(System.ComponentModel.ISynchronizeInvoke sync)
        {
            if (!connected)
            {
                this.sync = sync;
                //Connect to the wiimote
                try
                {
                    wm.Disconnect();
                    wm.Connect();
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine("Couldn't connect to the Wiimote.");
                    throw ex;
                }

                //Set the report type to return the IR sensor and accelerometer data (buttons always come back)
                wm.SetReportType(Wiimote.InputReport.IRAccel, true);
                wm.SetLEDs(true, false, false, true);

                //Setup the event to handle state changes
                wm.WiimoteChanged += new WiimoteChangedEventHandler(wm_WiimoteChanged);

                //Setup the envent to handle insertion/removal of extensions (nunchuck?)
                wm.WiimoteExtensionChanged += new WiimoteExtensionChangedEventHandler(wm_WiimoteExtensionChanged);
                connected = true;
            }
            else
            {
                throw new InvalidOperationException("Wiimote already connected.  Cannot reconnect.");
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (!(wm == null))
            {
                wm.Disconnect();
                wm.Dispose();
            }
            base.Dispose(disposing);
        }

        void wm_WiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs args)
        {
            if (args.Inserted)
            {
                wm.SetReportType(Wiimote.InputReport.IRExtensionAccel, true);
            }
            else
            {
                wm.SetReportType(Wiimote.InputReport.IRAccel, true);
            }
        }

        void wm_WiimoteChanged(object sender, WiimoteChangedEventArgs args)
        {
            //Get the current cursor location.
            System.Drawing.Point p;
            p = System.Windows.Forms.Cursor.Position;

            //Current state information
            WiimoteState ws = args.WiimoteState;

            #region Inverters
            if ((ws.ButtonState.One) && !oneDown)
            {
                InvertX = !InvertX;
                oneDown = true;
            }
            else if ((!ws.ButtonState.One) && oneDown)
            {
                oneDown = false;
            }

            if ((ws.ButtonState.Two) && twoDown)
            {
                InvertY = !InvertY;
                twoDown = true;
            }
            else if ((!ws.ButtonState.Two) && twoDown)
            {
                twoDown = false;
            }
            #endregion

            #region Mouse Buttons
            if (ws.ButtonState.A && !leftClickDown)
            {
                User32DLL.mouse_event(User32DLL.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, new System.IntPtr());
                leftClickDown = true;
            }
            else if (!ws.ButtonState.A && leftClickDown)
            {
                User32DLL.mouse_event(User32DLL.MOUSEEVENTF_LEFTUP, 0, 0, 0, new System.IntPtr());
                leftClickDown = false;
            }

            if (ws.ButtonState.B && !rightClickDown)
            {
                User32DLL.mouse_event(User32DLL.MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, new System.IntPtr());
                rightClickDown = true;
            }
            else if (!ws.ButtonState.B && rightClickDown)
            {
                User32DLL.mouse_event(User32DLL.MOUSEEVENTF_RIGHTUP, 0, 0, 0, new System.IntPtr());
                rightClickDown = false;
            }
            #endregion

            #region Fine Tuning
            //Increase the manual speed increase.
            if ((ws.ButtonState.Minus && !minusDown) && (PlusSpeed != 1))
            {
                PlusSpeed--;
                minusDown = true;
            }
            else if (!ws.ButtonState.Minus && minusDown)
            {
                minusDown = false;
            }
            if (ws.ButtonState.Plus && !plusDown)
            {
                PlusSpeed++;
                plusDown = true;
            }
            else if (!ws.ButtonState.Plus && plusDown)
            {
                plusDown = false;
            }

            //Check the dpad and move if neccessary.
            if ((ws.ButtonState.Left) && (p.X > XOrg) && (leftTick.AddMilliseconds(200) < System.DateTime.Now))
            { 
                p.X -= PlusSpeed;
                leftTick = DateTime.Now;
            }
            if ((ws.ButtonState.Right) && (p.X < XMax - 1) && (rightTick.AddMilliseconds(200) < System.DateTime.Now))
            { 
                p.X += PlusSpeed;
                rightTick = DateTime.Now;
            }
            if ((ws.ButtonState.Up) && (p.Y > YOrg) && (upTick.AddMilliseconds(200) < System.DateTime.Now))
            { 
                p.Y -= PlusSpeed;
                upTick = DateTime.Now;
            }
            if ((ws.ButtonState.Down) && (p.Y < YMax - 1) && (downTick.AddMilliseconds(200) < System.DateTime.Now))
            { 
                p.Y += PlusSpeed;
                downTick = DateTime.Now;
            }
            #endregion

            #region IR Movement and smoothing.
            if (ws.IRState.Found2)
            {
                //Used for smoothing algorithm
                float y;
                float x;
                float speed;
                float theta;
                float newX;
                float newY;

                //Rolls the history
                for (int i = 1; i < 30; i++)
                {
                    irCursorXHist[i] = irCursorXHist[i - 1];
                    irCursorYHist[i] = irCursorYHist[i - 1];
                }
                //Gets the new IR LED position
                irCursorXHist[0] = ws.IRState.MidX;
                irCursorYHist[0] = ws.IRState.MidY;

                //Gets the average over the last 30 strobes.
                irCursorX = 0; 
                irCursorY = 0;
                for (int i = 0; i < 30; i++)
                {
                    irCursorX += irCursorXHist[i];
                    irCursorY += irCursorYHist[i];
                }
                irCursorX = irCursorX / 30.0F;
                irCursorY = irCursorY / 30.0F;

                y = Math.Abs(irCursorYHist[0] - irCursorY);
                x = Math.Abs(irCursorXHist[0] - irCursorX);
                speed = (float)(Math.Sqrt(y * y + x * x) / 2.0);
                speed = speed * speed;

                if (speed > Math.Pow(10, -5))
                {
                    if (x == 0) { x += 0.00001F; y += 0.00001F; } //Fixes dev by zero error
                    theta = (float)Math.Tanh(y / x);

                    newX = (float)Math.Cos(theta) * speed;
                    newY = (float)Math.Tan(theta) * newX;

                    if (irCursorXHist[0] > irCursorX)
                    {
                        irCursorX = irCursorXHist[0] + newX;
                    }
                    else
                    {
                        irCursorX = irCursorXHist[0] - newX;
                    }
                    if (irCursorYHist[0] > irCursorY)
                    {
                        irCursorY = irCursorYHist[0] + newY;
                    }
                    else
                    {
                        irCursorY = irCursorYHist[0] - newY;
                    }

                    if (invertX)
                    {
                        irCursorX = (float)1.0 - irCursorX;
                    }
                    if (invertY)
                    {
                        irCursorY = (float)1.0 - irCursorY;
                    }
                }

                //ie.  position along the axis subtracted by offset.
                // eg.  Screen starts at -800 to 1200, width = 2000, 
                //ircursorX is 0.5, position is 1000, offset is -800 which would
                //put the cursor at position 200.
                p.X = (int)((irCursorX * XWidth) - XOrg);
                p.Y = (int)((irCursorY * YWidth) - YOrg);
            }
            else
            {
                //wm.SetLEDs(false, false, false, true);
            }
            #endregion

            System.Windows.Forms.Cursor.Position = p;
        }

        #region IDisposable Members

        private bool disposed = false;

        void IDisposable.Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                if (!(wm == null))
                {
                    wm.Disconnect();
                    wm.Dispose();
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        private delegate void StringDel(String info);

        public void NotifyPropertyChanged(String info)
        {
            if (sync != null) //Something to notify?
            {
                if (sync.InvokeRequired) //Cross thread, invoke events on the synchronized thread.
                {
                    StringDel d = new StringDel(NotifyPropertyChanged);
                    sync.Invoke(d, new Object[1] {info});
                }
                else //This is on the synchronize thread, so fire the event delegate queue.
                {
                    if (PropertyChanged != null) //Are there subscribers to the event?
                    {
                        PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(info));
                    }
                }
            }
        }

        #endregion
    }
}
